package com.yungu.swift.api.route.v2.passenger;

import com.alibaba.dubbo.config.annotation.Reference;
import com.fasterxml.jackson.databind.JsonNode;
import com.yungu.swift.api.base.gateway.GatewayHeader;
import com.yungu.swift.api.base.gateway.PassengerAccessParam;
import com.yungu.swift.api.base.gateway.RefreshTokenParam;
import com.yungu.swift.api.config.ApplicationConfig;
import com.yungu.swift.api.config.NacosListener;
import com.yungu.swift.api.utils.JwtHelper;
import com.yungu.swift.api.utils.RandomUtil;
import com.yungu.swift.api.wxapi.decrypt.WXBizDataCrypt;
import com.yungu.swift.assets.passenger.model.dto.PassengerDto;
import com.yungu.swift.assets.passenger.model.param.ApiPassengerLoginParam;
import com.yungu.swift.assets.passenger.model.vo.PassengerVo;
import com.yungu.swift.assets.passenger.service.PassengerService;
import com.yungu.swift.autoconfig.redis.RedisStringCacheService;
import com.yungu.swift.base.ResponseData;
import com.yungu.swift.base.model.api.ApiParam;
import com.yungu.swift.common.model.dto.CommonIdentifyDto;
import com.yungu.swift.common.service.CommonSmsService;
import com.yungu.swift.constants.CommonConstant;
import com.yungu.swift.constants.MagicConstant;
import com.yungu.swift.utils.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.URLDecoder;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import static com.yungu.swift.api.base.ApiErrorEnum.*;
import static com.yungu.swift.api.base.BaseApi.getUserIP;

/**
 * @author : cuixiuyin
 * @date : 2019/10/28
 */
@Slf4j
@RestController("pasLoginApiV2")
@RequestMapping(value = "/api/v2/passenger")
@Api(value = "【乘客】乘客登入/登出接口V2", tags = "V2-乘客登入/登出接口")
public class PasLoginApi {

    @Autowired
    private RedisStringCacheService redisStringCacheService;

    @Reference
    private PassengerService passengerService;
    @Reference
    private CommonSmsService commonSmsService;

    public static final String ACCESS_TOKEN_PREFFIX = "PASSENGER_ACCESS_TOKEN_";
    public static final String REFRESH_TOKEN_PREFFIX = "PASSENGER_REFRESH_TOKEN_";
    public static final String PASSENGER_UUID = "PASSENGER_UUID_";

    private static final Integer ACCESS_TOKEN_EXPIRE = 2 * 60;


    @ApiOperation(value = "乘客登入接口")
    @PostMapping(value = {"/login"})
    public ResponseData<Map<String, String>> login(
            @RequestHeader String appid, @RequestBody ApiPassengerLoginParam loginParam, HttpServletRequest request) {
        if (ParamUtil.isIllegal(loginParam.getMobile(), loginParam.getIdentifyCode(), loginParam.getMac())) {
            return ResponseData.buildErrorResponse(PARAM_ERROR.getCode(), PARAM_ERROR.getMsg());
        }
        loginParam.setAppid(appid);
        String decryptMobile = RandomUtil.randomDecrypt(loginParam.getMobile());
        //判断电话是否准确
        if (!ValidateUtils.validateMobile(decryptMobile)) {
            return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "请输入正确的手机号");
        }
        //判断验证码格式是否正确
        String decryptIdentifyCode = RandomUtil.randomDecrypt(loginParam.getIdentifyCode());
        if (decryptIdentifyCode.length() != MagicConstant.INT_FOUR) {
            return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "请输入正确的验证码");
        }
        // 使用 mac 地址作为设备唯一标识符号
        String decryptMac = RandomUtil.randomDecrypt(loginParam.getMac());
        if (StringUtils.isEmpty(decryptMac)) {
            return ResponseData.buildErrorResponse(PARAM_ERROR.getCode(), PARAM_ERROR.getMsg());
        }
        loginParam.setMobile(decryptMobile);
        loginParam.setIdentifyCode(decryptIdentifyCode);
        loginParam.setMac(decryptMac);
        //验证码限制三分钟只能输错5次
        int loginCount = 1;
        String loginCountCache = redisStringCacheService.get("passenger_login_count_" + decryptMobile);
        if (loginCountCache != null) {
            loginCount = Integer.parseInt(loginCountCache);
        }
        if (loginCount > 5) {
            return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "已连续输错5次，3分钟内不允许登陆");
        }
        // 检验验证码
        CommonIdentifyDto commonIdentifyDto = null;
        if (!decryptMobile.substring(0, 3).equals(MagicConstant.STRING_ONE_FOUR_TWO)) {
            Map<String, Object> paramMap = MapUtils.build(8);
            paramMap.put("mobile", decryptMobile);
            paramMap.put("identifyCode", decryptIdentifyCode);
            paramMap.put("status", CommonConstant.IDENTIFY_CODE_STATUS_UNUSED);
            if ((commonIdentifyDto = commonSmsService.get(paramMap).getData()) == null) {
                redisStringCacheService.set("passenger_login_count_" + decryptMobile, ++loginCount + "", 3);
                return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "验证码错误");
            }
            redisStringCacheService.del("passenger_login_count_" + decryptMobile);
            if (LocalDateTime.now().isAfter(commonIdentifyDto.getCreateOn().toInstant().atZone(ZoneId.systemDefault())
                    .toLocalDateTime().plusMinutes(2))) {
                return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "验证码已过期，请重新获取");
            }
        }
        // 执行登陆操作
        if (StringUtils.isEmpty(loginParam.getIp())) {
            loginParam.setIp(getUserIP(request));
        }
        ResponseData<PassengerVo> responseData = passengerService.login(loginParam);
        if (!responseData.isSuccess() || responseData.getData() == null) {
            return ResponseData.buildErrorResponse(responseData.getErrCode(), responseData.getMsg());
        }
        ApiParam apiParam = new ApiParam();
        apiParam.setRid(CommonConstant.PASSENGER);
        apiParam.setMobile(responseData.getData().getMobile());
        apiParam.setUserUuid(responseData.getData().getUuid());
        apiParam.setAppid(responseData.getData().getAppid());
        apiParam.setTs(System.currentTimeMillis());
        // 删除该用户已经关联的 token，防止重复登陆
        logout(null, apiParam);
        Map<String, String> loginVo = parseLoginVo(apiParam, decryptMac);
        //用户的验证码设置为已使用
        if (commonIdentifyDto != null) {
            commonIdentifyDto.setStatus(CommonConstant.IDENTIFY_CODE_STATUS_USED);
            final CommonIdentifyDto finalCommonIdentifyDto = commonIdentifyDto;
            //更新验证码
            CompletableFuture.runAsync(() -> commonSmsService.edit(finalCommonIdentifyDto));
        }
        return ResponseData.buildSuccessResponse(loginVo);
    }

    @ApiOperation(value = "乘客刷新 token 接口")
    @PostMapping(value = {"/refreshToken"})
    public ResponseData<Map<String, String>> refreshToken(@RequestHeader String accessToken,
                                                          @RequestBody RefreshTokenParam refreshTokenParam) {
        if (ParamUtil.isIllegal(refreshTokenParam.getRefreshToken())) {
            return ResponseData.buildErrorResponse(PARAM_ERROR.getCode(), PARAM_ERROR.getMsg());
        }
        String jsonRefreshTokenMap = redisStringCacheService.get(REFRESH_TOKEN_PREFFIX + refreshTokenParam.getRefreshToken(), false);
        if (StringUtils.isEmpty(jsonRefreshTokenMap)) {
            return ResponseData.buildErrorResponse(LOGIN_EXPIRED.getCode(), LOGIN_EXPIRED.getMsg());
        }
        redisStringCacheService.del(REFRESH_TOKEN_PREFFIX + refreshTokenParam.getRefreshToken(), false);
        redisStringCacheService.del(ACCESS_TOKEN_PREFFIX + accessToken, false);
        String decryptMac = RandomUtil.randomDecrypt(refreshTokenParam.getMac());
        try {
            Map<String, String> refreshTokenMap = JsonUtils.readValue(jsonRefreshTokenMap, Map.class);
            if (StringUtils.isNotEmpty(decryptMac) && !decryptMac.equals(refreshTokenMap.get("mac"))) {
                return ResponseData.buildErrorResponse(TOKEN_NO_SAFETY.getCode(), TOKEN_NO_SAFETY.getMsg());
            }
            ApiParam apiParam = JsonUtils.readValue(refreshTokenMap.get("apiParam"), ApiParam.class);
            return ResponseData.buildSuccessResponse(parseLoginVo(apiParam, decryptMac));
        } catch (IOException e) {
            log.error("PassengerLoginApi refreshToken error. refreshToken info :{} ", jsonRefreshTokenMap);
        }
        return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "接口异常");
    }


    private Map<String, String> parseLoginVo(ApiParam apiParam, String mac) {
        String accessToken = StringUtils.buildUUID();
        String refreshToken = StringUtils.buildUUID();
        // accessToken
        String jsonApiParam = JsonUtils.toJSONString(apiParam);
        redisStringCacheService.set(ACCESS_TOKEN_PREFFIX + accessToken, jsonApiParam, ACCESS_TOKEN_EXPIRE, TimeUnit.MINUTES, false);
        // refreshToken
        Map<String, Object> refreshTokenMap = MapUtils.build(4);
        refreshTokenMap.put("apiParam", jsonApiParam);
        refreshTokenMap.put("mac", mac);
        redisStringCacheService.set(REFRESH_TOKEN_PREFFIX + refreshToken, JsonUtils.toJSONString(refreshTokenMap), ApplicationConfig.SERVER_CONFIG.getSafety().getLoginExp(), TimeUnit.DAYS, false);
        // passengerUuid
        Map<String, String> tokenMap = new HashMap<>(4);
        tokenMap.put(GatewayHeader.accessToken, accessToken);
        tokenMap.put("refreshToken", refreshToken);
        redisStringCacheService.set(PASSENGER_UUID + apiParam.getUserUuid(), JsonUtils.toJSONString(tokenMap), 12 * 60, TimeUnit.MINUTES, false);
        Map<String, String> resultMap = new HashMap<>(8);
        resultMap.put("accessToken", accessToken);
        resultMap.put("refreshToken", refreshToken);
        resultMap.put("jwts", JwtHelper.createJWT(apiParam.getUserUuid(), jsonApiParam));
        resultMap.put("userUuid", apiParam.getUserUuid());

        PassengerDto passengerDto = new PassengerDto();
        passengerDto.setUuid(apiParam.getUserUuid());
        passengerDto.setToken(accessToken);
        passengerService.edit(passengerDto);
        return resultMap;
    }


    @ApiOperation(value = "乘客登出接口")
    @PostMapping(value = {"/logout"})
    public ResponseData<Boolean> logout(@RequestHeader String accessToken, @RequestBody ApiParam apiParam) {
        String jsonTokenMap = redisStringCacheService.get(PASSENGER_UUID + apiParam.getUserUuid(), false);
        if (StringUtils.isEmpty(jsonTokenMap)) {
            return ResponseData.buildSuccessResponse("退出成功", true);
        }
        try {
            Map<String, String> tokenMap = JsonUtils.readValue(jsonTokenMap, Map.class);
            String passengerAccessToken = tokenMap.get(GatewayHeader.accessToken);
            String passengerRefreshToken = tokenMap.get("refreshToken");
            redisStringCacheService.del(PASSENGER_UUID + apiParam.getUserUuid(), false);
            redisStringCacheService.del(REFRESH_TOKEN_PREFFIX + passengerRefreshToken, false);
            redisStringCacheService.del(ACCESS_TOKEN_PREFFIX + passengerAccessToken, false);
        } catch (IOException e) {
            log.error("PassengerLoginApi logout readValue error.{}", jsonTokenMap);
        } finally {
            redisStringCacheService.del(ACCESS_TOKEN_PREFFIX + accessToken, false);
        }
        return ResponseData.buildSuccessResponse("退出成功", true);
    }

    @ApiOperation(value = "小程序乘客登陆")
    @PostMapping(value = {"/login/wx/applet/verify"})
    public ResponseData<Map<String, String>> wxAppletAccessLogin(@RequestHeader String appid,
                                                                 @RequestBody PassengerAccessParam wxAccessParam,
                                                                 HttpServletRequest request) throws IOException {
        if (ParamUtil.isIllegal(wxAccessParam.getCode())) {
            return ResponseData.buildErrorResponse(PARAM_ERROR.getCode(), PARAM_ERROR.getMsg());
        }
        // 1. 得到 openId 和 sessionKey
        String appletWxAppid = NacosListener.WXPAY_CONFIG.getPassengerAppletWxAppid();
        String appletWxAppSecret = NacosListener.WXPAY_CONFIG.getPassengerAppletWxAppSecret();
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appletWxAppid + "&secret=" +
                appletWxAppSecret + "&js_code=" + wxAccessParam.getCode() + "&grant_type=authorization_code";
        String jscode2session = HttpUtils.getResponseString(url);
        if (StringUtils.isEmpty(jscode2session)) {
            return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "登陆凭证接口请求异常");
        }
        JsonNode codeNode = JsonUtils.readNode(jscode2session);
        if (codeNode.get("errcode") != null) {
            return ResponseData.buildErrorResponse(codeNode.get("errcode").asInt(0), codeNode.get("errmsg").asText());
        }
        String openid = codeNode.get("openid").asText();
        String sessionKey = codeNode.get("session_key").asText();
        PassengerDto passengerDto = passengerService.get(MapUtils.build("openId", openid)).getData();
        // 非首次登陆直接走登陆流程
        ApiPassengerLoginParam loginParam = new ApiPassengerLoginParam();
        loginParam.setAppid(appid);
        loginParam.setIp(getUserIP(request));
        loginParam.setMac("");
        loginParam.setOpenId(openid);
        if (passengerDto != null) {
            loginParam.setMobile(passengerDto.getMobile());
        } else {
            if (ParamUtil.isIllegal(wxAccessParam.getEncrypteData(), wxAccessParam.getIv())) {
                return ResponseData.buildErrorResponse(WX_APPLET_DECRYPT_ERROR.getCode(), WX_APPLET_DECRYPT_ERROR.getMsg());
            }
            // 2. 获取敏感数据
            JsonNode decryptNode = WXBizDataCrypt.decrypt(sessionKey, wxAccessParam.getEncrypteData(), wxAccessParam.getIv());
            if (decryptNode == null) {
                return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "获取用户敏感数据失败");
            }
            loginParam.setMobile(decryptNode.get("purePhoneNumber").asText());
        }
        ResponseData<PassengerVo> responseData = passengerService.login(loginParam);
        if (!responseData.isSuccess() || responseData.getData() == null) {
            return ResponseData.buildErrorResponse(responseData.getErrCode(), responseData.getMsg());
        }
        return ResponseData.buildSuccessResponse("登陆成功", getLoginVo(responseData.getData(), ""));
    }


    @ApiOperation(value = "小程序乘客登陆凭证校验", notes = "微信接口请求错误则返回失败；成功则返回 appid、session_key；若有对应的绑定用户，则同时返回用户信息")
    @PostMapping(value = {"/wx/applet/verify"})
    public ResponseData<Map<String, Object>> wxAppletVerify(@RequestHeader String appid,
                                                            @RequestBody PassengerAccessParam wxAccessParam) throws IOException {
        if (StringUtils.isEmpty(wxAccessParam.getCode())) {
            return ResponseData.buildErrorResponse(PARAM_ERROR.getCode(), PARAM_ERROR.getMsg());
        }
        String appletWxAppid = NacosListener.WXPAY_CONFIG.getPassengerAppletWxAppid();
        String appletWxAppSecret = NacosListener.WXPAY_CONFIG.getPassengerAppletWxAppSecret();
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appletWxAppid + "&secret=" +
                appletWxAppSecret + "&js_code=" + wxAccessParam.getCode() + "&grant_type=authorization_code";
        String responseStr = HttpUtils.getResponseString(url);
        if (StringUtils.isEmpty(responseStr)) {
            return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "登陆凭证接口请求异常");
        }
        JsonNode jsonNode = JsonUtils.readNode(responseStr);
        if (jsonNode.get("errcode") != null) {
            return ResponseData.buildErrorResponse(jsonNode.get("errcode").asInt(0), jsonNode.get("errmsg").asText());
        }
        return ResponseData.buildSuccessResponse("成功获取绑定用户", MapUtils.build("openId", jsonNode.get("openid").asText()));
    }

    @ApiOperation(value = "乘客微信登陆")
    @PostMapping(value = {"/login/wx"})
    public ResponseData<Map<String, String>> wxPasAccessLogin(@RequestHeader String appid,
                                                              @RequestBody PassengerAccessParam accessParam, HttpServletRequest request) throws IOException {
        if (StringUtils.isEmpty(accessParam.getCode())) {
            return ResponseData.buildErrorResponse(PARAM_ERROR.getCode(), PARAM_ERROR.getMsg());
        }
        String passengerWxAppid = NacosListener.WXPAY_CONFIG.getPassengerWxAppid();
        String passengerWxAppSecret = NacosListener.WXPAY_CONFIG.getPassengerWxAppSecret();
        // 1. 授权登陆
        String authUrl = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
                passengerWxAppid, passengerWxAppSecret, accessParam.getCode());
        String authorization = HttpUtils.getResponseString(authUrl);
        if (StringUtils.isEmpty(authorization)) {
            return ResponseData.buildErrorResponse(ResponseData.ERROR_CODE, "微信方登陆异常，请稍后再试");
        }
        JsonNode authNode = JsonUtils.readNode(authorization);
        System.out.println(JsonUtils.toJSONString(authNode));
        if (authNode.get("errcode") != null) {
            return ResponseData.buildErrorResponse(authNode.get("errcode").asInt(0), authNode.get("errmsg").toString());
        }
        String accessToken = authNode.get("access_token").asText();
        String openid = authNode.get("openid").asText();
        // 2. 获取用户信息
        String userInfoUrl = String.format("https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s",
                URLDecoder.decode(accessToken, "utf-8"), URLDecoder.decode(openid, "utf-8"));
        String userinfo = HttpUtils.getResponseString(userInfoUrl);
        JsonNode userNode = JsonUtils.readNode(userinfo);
        if (userNode.get("errcode") != null) {
            return ResponseData.buildErrorResponse(userNode.get("errcode").asInt(0), userNode.get("errmsg").toString());
        }
        String decryptMac = RandomUtil.randomDecrypt(accessParam.getMac());
        PassengerDto passengerDto = passengerService.get(MapUtils.build("openId", openid)).getData();
        // 非首次登陆直接走登陆流程
        if (passengerDto != null) {
            ApiPassengerLoginParam loginParam = new ApiPassengerLoginParam();
            loginParam.setAppid(appid);
            loginParam.setMobile(passengerDto.getMobile());
            loginParam.setMac(decryptMac);
            loginParam.setOpenId(openid);
            loginParam.setIp(getUserIP(request));
            ResponseData<PassengerVo> responseData = passengerService.login(loginParam);
            if (!responseData.isSuccess() || responseData.getData() == null) {
                return ResponseData.buildErrorResponse(responseData.getErrCode(), responseData.getMsg());
            }
            return ResponseData.buildSuccessResponse("登陆成功", getLoginVo(responseData.getData(), decryptMac));
        }
        Map<String, String> result = new HashMap<>(4);
        result.put("openId", openid);
        return ResponseData.buildSuccessResponse("成功获取绑定用户", result);
    }

    private Map<String, String> getLoginVo(PassengerVo passengerVo, String decryptMac) {
        ApiParam apiParam = new ApiParam();
        apiParam.setRid(CommonConstant.PASSENGER);
        apiParam.setMobile(passengerVo.getMobile());
        apiParam.setUserUuid(passengerVo.getUuid());
        apiParam.setAppid(passengerVo.getAppid());
        apiParam.setTs(System.currentTimeMillis());
        // 删除该用户已经关联的 token，防止重复登陆
        logout(null, apiParam);
        return parseLoginVo(apiParam, decryptMac);
    }

}
